package hotnet.future;

import hotnet.exception.ExceptionMonitor;
import hotnet.listener.FutureListener;
import hotnet.session.IoSession;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class DefaultIoFuture implements IoFuture {
	 /** A number of seconds to wait between two deadlock controls ( 5 seconds ) */
    private static final long DEAD_LOCK_CHECK_INTERVAL = 5000L;

    /** The associated session */
    private final IoSession session;

    /** A lock used by the wait() method */
    private final Object lock;
    
    private List<FutureListener> listeners;

    private Object result;

    private boolean ready;

    private int waiters;
    
    public DefaultIoFuture(IoSession session) {
    	this.session = session;
    	this.lock = this;
    }
    
    @Override
	public IoSession getSession() {
		return session;
	}
	

    public Throwable getException() {
        if (isDone()) {
            Object v = getValue();
            if (v instanceof ExceptionHolder) {
                return ((ExceptionHolder) v).throwable;
            }
        }
        return null;
    }
	
    public void setException(Throwable exception) {
        if (exception == null) {
            throw new IllegalArgumentException("exception");
        }

        setValue(new ExceptionHolder(exception));
    }

	private void checkDeadLock() {
		
		return;
//        // Only read / write / connect / write future can cause dead lock.
//        if (!(this instanceof CloseFuture || this instanceof WriteFuture || this instanceof ReadFuture || this instanceof ConnectFuture)) {
//            return;
//        }
//
//        // Get the current thread stackTrace.
//        // Using Thread.currentThread().getStackTrace() is the best solution,
//        // even if slightly less efficient than doing a new Exception().getStackTrace(),
//        // as internally, it does exactly the same thing. The advantage of using
//        // this solution is that we may benefit some improvement with some
//        // future versions of Java.
//        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
//
//        // Simple and quick check.
//        for (StackTraceElement s : stackTrace) {
//            if (AbstractPollingIoProcessor.class.getName().equals(s.getClassName())) {
//                IllegalStateException e = new IllegalStateException("t");
//                e.getStackTrace();
//                throw new IllegalStateException("DEAD LOCK: " + IoFuture.class.getSimpleName()
//                        + ".await() was invoked from an I/O processor thread.  " + "Please use "
//                        + IoFutureListener.class.getSimpleName() + " or configure a proper thread model alternatively.");
//            }
//        }
//
//        // And then more precisely.
//        for (StackTraceElement s : stackTrace) {
//            try {
//                Class<?> cls = DefaultIoFuture.class.getClassLoader().loadClass(s.getClassName());
//                if (IoProcessor.class.isAssignableFrom(cls)) {
//                    throw new IllegalStateException("DEAD LOCK: " + IoFuture.class.getSimpleName()
//                            + ".await() was invoked from an I/O processor thread.  " + "Please use "
//                            + IoFutureListener.class.getSimpleName()
//                            + " or configure a proper thread model alternatively.");
//                }
//            } catch (Exception cnfe) {
//                // Ignore
//            }
//        }
    }
	
	private boolean await0(long timeoutMillis, boolean interruptable) throws InterruptedException {
		long endTime = System.currentTimeMillis() + timeoutMillis;
		if (endTime < 0) {
            endTime = Long.MAX_VALUE;
        }
		
		synchronized (lock) {
			if (ready) {
				return true;
			} else if (timeoutMillis <= 0) {
				return ready;
			}
			
			waiters++;
			
			try {
				for (;;) {
					try {
						long timeOut = Math.min(timeoutMillis, DEAD_LOCK_CHECK_INTERVAL);
						lock.wait(timeOut);
					} catch (InterruptedException e) {
						if (interruptable) {
							throw e;
						}
					}
					
					if (ready) {
						return true;
					}
					
					if (endTime < System.currentTimeMillis()) {
						return ready;
					}
					
					checkDeadLock();
				}
			} finally {
				waiters--;
			}
		}
	}
	
	public IoFuture await() throws InterruptedException {
		synchronized (lock) {
			while (!ready) {
				 waiters++;
	                try {
	                    lock.wait(DEAD_LOCK_CHECK_INTERVAL);
	                } finally {
	                    waiters--;
	                    if (!ready) {
	                         checkDeadLock();
	                    }
	                }
			}
		}
		return this;
	}

    public boolean await(long timeout, TimeUnit unit) throws InterruptedException {
        return await(unit.toMillis(timeout));
    }

	public boolean await(long timeoutMillis) throws InterruptedException {
		return await0(timeoutMillis, true);
	}

	public IoFuture awaitUninterruptibly() {
		try {
			await0(Long.MAX_VALUE, false);
		} catch (InterruptedException ie) {
			// Do nothing : this catch is just mandatory by contract
		}

		return this;
	}

	public boolean awaitUninterruptibly(long timeout, TimeUnit unit) {
		return awaitUninterruptibly(unit.toMillis(timeout));
	}

	public boolean awaitUninterruptibly(long timeoutMillis) {
		try {
			return await0(timeoutMillis, false);
		} catch (InterruptedException e) {
			throw new InternalError();
		}
	}

	public boolean isDone() {
		synchronized (lock) {
            return ready;
        }
	}

	public IoFuture addListener(FutureListener listener) {
		if (listener == null) {
			throw new IllegalArgumentException("listener");
		}
		
		boolean notifyNow = false;
        synchronized (lock) {
			if (ready) {
				notifyNow = true;
			} else {
				if (listeners == null) {
					listeners = new ArrayList<FutureListener>(1);
				}
				listeners.add(listener);
			}
        }

        if (notifyNow) {
            notifyListener(listener);
        }
		
		return this;
	}
	
	private void notifyListeners() {

		if (listeners != null) {
			for (FutureListener l : listeners) {
				notifyListener(l);
			}
			listeners = null;
		}
	}

	private void notifyListener(FutureListener l) {
        try {
            l.operationComplete(this);
        } catch (Exception e) {
            ExceptionMonitor.getInstance().exceptionCaught(e);
        }
    }

	public IoFuture removeListener(FutureListener listener) {
		synchronized (lock) {
			if (listeners != null) {
				listeners.remove(listener);
			}
		}
		
		return this;
	}

	
    public void setValue(Object newValue) {
        synchronized (lock) {
            if (ready) {
                return;
            }

            result = newValue;
            ready = true;
            if (waiters > 0) {
                lock.notifyAll();
            }
        }

        notifyListeners();
    }
    
    protected Object getValue() {
        synchronized (lock) {
            return result;
        }
    }
    
    private static class ExceptionHolder {
    	private Throwable throwable;
    	
    	public ExceptionHolder(Throwable throwable) {
    		this.throwable = throwable;
    	}
    }

}
